/* 
 * Licensed to Aduna under one or more contributor license agreements.  
 * See the NOTICE.txt file distributed with this work for additional 
 * information regarding copyright ownership. 
 *
 * Aduna licenses this file to you under the terms of the Aduna BSD 
 * License (the "License"); you may not use this file except in compliance 
 * with the License. See the LICENSE.txt file distributed with this work 
 * for the full License.
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
 * implied. See the License for the specific language governing permissions
 * and limitations under the License.
 */
package org.openrdf.query.parser.resparql;

import java.util.LinkedHashSet;
import java.util.Set;

import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.parser.resparql.ast.ASTProjectionElem;
import org.openrdf.query.parser.resparql.ast.ASTQuery;
import org.openrdf.query.parser.resparql.ast.ASTQueryContainer;
import org.openrdf.query.parser.resparql.ast.ASTRecommend;
import org.openrdf.query.parser.resparql.ast.ASTRecommendQuery;
import org.openrdf.query.parser.resparql.ast.ASTVar;
import org.openrdf.query.parser.resparql.ast.ASTWhereClause;
import org.openrdf.query.parser.resparql.ast.Node;
import org.openrdf.query.parser.resparql.ast.SyntaxTreeBuilderTreeConstants;
import org.openrdf.query.parser.resparql.ast.VisitorException;

/**
 * Processes 'wildcard' projections, making them explicit by adding the
 * appropriate variable nodes to them.
 * 
 * @author arjohn
 * @author Jeen Broekstra
 */
public class WildcardProjectionProcessor extends ASTVisitorBase {

	public static void process(ASTQueryContainer qc)
		throws MalformedQueryException
	{
		ASTQuery queryNode = qc.getQuery();

		// scan for nested RECOMMEND clauses in the query
		if (queryNode != null) {
			ASTWhereClause queryBody = queryNode.getWhereClause();

			// DESCRIBE queries can be without a query body sometimes
			if (queryBody != null) {
				RecommendClauseCollector collector = new RecommendClauseCollector();
				try {
					queryBody.jjtAccept(collector, null);

					Set<ASTRecommend> recommendClauses = collector.getRecommendClauses();

					for (ASTRecommend recommendClause : recommendClauses) {
						if (recommendClause.isWildcard()) {
							ASTRecommendQuery q = (ASTRecommendQuery)recommendClause.jjtGetParent();

							addQueryVars(q.getWhereClause(), recommendClause);
							recommendClause.setWildcard(false);
						}
					}

				}
				catch (VisitorException e) {
					throw new MalformedQueryException(e);
				}
			}
		}

		if (queryNode instanceof ASTRecommendQuery) {
			// check for wildcard in upper RECOMMEND query

			ASTRecommendQuery recommendQuery = (ASTRecommendQuery)queryNode;
			ASTRecommend recommendClause = recommendQuery.getRecommend();
			if (recommendClause.isWildcard()) {
				addQueryVars(recommendQuery.getWhereClause(), recommendClause);
				recommendClause.setWildcard(false);
			}
		}
	}

	private static void addQueryVars(ASTWhereClause queryBody, Node wildcardNode)
		throws MalformedQueryException
	{
		QueryVariableCollector visitor = new QueryVariableCollector();

		try {
			// Collect variable names from query
			queryBody.jjtAccept(visitor, null);

			// Adds ASTVar nodes to the ASTProjectionElem nodes and to the parent
			for (String varName : visitor.getVariableNames()) {
				ASTVar varNode = new ASTVar(SyntaxTreeBuilderTreeConstants.JJTVAR);
				ASTProjectionElem projectionElemNode = new ASTProjectionElem(
						SyntaxTreeBuilderTreeConstants.JJTPROJECTIONELEM);

				varNode.setName(varName);
				projectionElemNode.jjtAppendChild(varNode);
				varNode.jjtSetParent(projectionElemNode);

				wildcardNode.jjtAppendChild(projectionElemNode);
				projectionElemNode.jjtSetParent(wildcardNode);

			}

		}
		catch (VisitorException e) {
			throw new MalformedQueryException(e);
		}
	}

	/*------------------------------------*
	 * Inner class QueryVariableCollector *
	 *------------------------------------*/

	private static class QueryVariableCollector extends ASTVisitorBase {

		private Set<String> variableNames = new LinkedHashSet<String>();

		public Set<String> getVariableNames() {
			return variableNames;
		}

		@Override
		public Object visit(ASTRecommendQuery node, Object data)
			throws VisitorException
		{
			// stop visitor from processing body of sub-recommend, only add variables
			// from the projection
			return visit(node.getRecommend(), data);
		}

		@Override
		public Object visit(ASTProjectionElem node, Object data)
			throws VisitorException
		{
			// only include the actual alias from a projection element in a
			// subrecommend, not any variables used as
			// input to a function
			String alias = node.getAlias();
			if (alias != null) {
				variableNames.add(alias);
				return null;
			}
			else {
				return super.visit(node, data);
			}
		}

		@Override
		public Object visit(ASTVar node, Object data)
			throws VisitorException
		{
			if (!node.isAnonymous()) {
				variableNames.add(node.getName());
			}
			return super.visit(node, data);
		}
	}

	/*------------------------------------*
	 * Inner class RecommendClauseCollector  *
	 *------------------------------------*/

	private static class RecommendClauseCollector extends ASTVisitorBase {

		private Set<ASTRecommend> recommendClauses = new LinkedHashSet<ASTRecommend>();

		public Set<ASTRecommend> getRecommendClauses() {
			return recommendClauses;
		}

		@Override
		public Object visit(ASTRecommend node, Object data)
			throws VisitorException
		{
			recommendClauses.add(node);
			return super.visit(node, data);
		}
	}
}
